summaryrefslogtreecommitdiff
path: root/spec/requests/api/graphql
diff options
context:
space:
mode:
Diffstat (limited to 'spec/requests/api/graphql')
-rw-r--r--spec/requests/api/graphql/boards/board_lists_query_spec.rb3
-rw-r--r--spec/requests/api/graphql/ci/group_variables_spec.rb67
-rw-r--r--spec/requests/api/graphql/ci/instance_variables_spec.rb60
-rw-r--r--spec/requests/api/graphql/ci/job_spec.rb4
-rw-r--r--spec/requests/api/graphql/ci/jobs_spec.rb6
-rw-r--r--spec/requests/api/graphql/ci/manual_variables_spec.rb95
-rw-r--r--spec/requests/api/graphql/ci/pipelines_spec.rb4
-rw-r--r--spec/requests/api/graphql/ci/project_variables_spec.rb67
-rw-r--r--spec/requests/api/graphql/ci/runner_spec.rb2
-rw-r--r--spec/requests/api/graphql/ci/stages_spec.rb2
-rw-r--r--spec/requests/api/graphql/container_repository/container_repository_details_spec.rb2
-rw-r--r--spec/requests/api/graphql/crm/contacts_spec.rb69
-rw-r--r--spec/requests/api/graphql/current_user/groups_query_spec.rb34
-rw-r--r--spec/requests/api/graphql/group/container_repositories_spec.rb2
-rw-r--r--spec/requests/api/graphql/group/dependency_proxy_blobs_spec.rb2
-rw-r--r--spec/requests/api/graphql/group/dependency_proxy_group_setting_spec.rb2
-rw-r--r--spec/requests/api/graphql/group/dependency_proxy_image_ttl_policy_spec.rb2
-rw-r--r--spec/requests/api/graphql/group/dependency_proxy_manifests_spec.rb7
-rw-r--r--spec/requests/api/graphql/group/group_members_spec.rb48
-rw-r--r--spec/requests/api/graphql/mutations/issues/create_spec.rb36
-rw-r--r--spec/requests/api/graphql/mutations/notes/create/diff_note_spec.rb14
-rw-r--r--spec/requests/api/graphql/mutations/snippets/update_spec.rb17
-rw-r--r--spec/requests/api/graphql/mutations/work_items/create_from_task_spec.rb1
-rw-r--r--spec/requests/api/graphql/mutations/work_items/create_spec.rb89
-rw-r--r--spec/requests/api/graphql/mutations/work_items/delete_task_spec.rb2
-rw-r--r--spec/requests/api/graphql/mutations/work_items/update_spec.rb268
-rw-r--r--spec/requests/api/graphql/mutations/work_items/update_widgets_spec.rb51
-rw-r--r--spec/requests/api/graphql/project/container_repositories_spec.rb2
-rw-r--r--spec/requests/api/graphql/project/issues_spec.rb37
-rw-r--r--spec/requests/api/graphql/project/jobs_spec.rb4
-rw-r--r--spec/requests/api/graphql/project/packages_cleanup_policy_spec.rb2
-rw-r--r--spec/requests/api/graphql/project/pipeline_spec.rb60
-rw-r--r--spec/requests/api/graphql/project/project_members_spec.rb48
-rw-r--r--spec/requests/api/graphql/todo_query_spec.rb50
-rw-r--r--spec/requests/api/graphql/work_item_spec.rb98
35 files changed, 1155 insertions, 102 deletions
diff --git a/spec/requests/api/graphql/boards/board_lists_query_spec.rb b/spec/requests/api/graphql/boards/board_lists_query_spec.rb
index eb206465bce..39ff108a9e1 100644
--- a/spec/requests/api/graphql/boards/board_lists_query_spec.rb
+++ b/spec/requests/api/graphql/boards/board_lists_query_spec.rb
@@ -96,7 +96,8 @@ RSpec.describe 'get board lists' do
context 'when ascending' do
it_behaves_like 'sorted paginated query' do
- let(:sort_param) { }
+ include_context 'no sort argument'
+
let(:first_param) { 2 }
let(:all_records) { lists.map { |list| a_graphql_entity_for(list) } }
end
diff --git a/spec/requests/api/graphql/ci/group_variables_spec.rb b/spec/requests/api/graphql/ci/group_variables_spec.rb
new file mode 100644
index 00000000000..5ea6646ec2c
--- /dev/null
+++ b/spec/requests/api/graphql/ci/group_variables_spec.rb
@@ -0,0 +1,67 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe 'Query.group(fullPath).ciVariables' do
+ include GraphqlHelpers
+
+ let_it_be(:group) { create(:group) }
+ let_it_be(:user) { create(:user) }
+
+ let(:query) do
+ %(
+ query {
+ group(fullPath: "#{group.full_path}") {
+ ciVariables {
+ nodes {
+ id
+ key
+ value
+ variableType
+ protected
+ masked
+ raw
+ environmentScope
+ }
+ }
+ }
+ }
+ )
+ end
+
+ context 'when the user can administer the group' do
+ before do
+ group.add_owner(user)
+ 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')
+
+ post_graphql(query, current_user: user)
+
+ expect(graphql_data.dig('group', 'ciVariables', 'nodes')).to contain_exactly({
+ 'id' => variable.to_global_id.to_s,
+ 'key' => 'TEST_VAR',
+ 'value' => 'test',
+ 'variableType' => 'ENV_VAR',
+ 'masked' => false,
+ 'protected' => true,
+ 'raw' => true,
+ 'environmentScope' => 'staging'
+ })
+ end
+ end
+
+ context 'when the user cannot administer the group' do
+ it 'returns nothing' do
+ create(:ci_group_variable, group: group, value: 'verysecret', masked: true)
+
+ group.add_developer(user)
+
+ post_graphql(query, current_user: user)
+
+ expect(graphql_data.dig('group', 'ciVariables')).to be_nil
+ end
+ end
+end
diff --git a/spec/requests/api/graphql/ci/instance_variables_spec.rb b/spec/requests/api/graphql/ci/instance_variables_spec.rb
new file mode 100644
index 00000000000..7acf73a4e7a
--- /dev/null
+++ b/spec/requests/api/graphql/ci/instance_variables_spec.rb
@@ -0,0 +1,60 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe 'Query.ciVariables' do
+ include GraphqlHelpers
+
+ let(:query) do
+ %(
+ query {
+ ciVariables {
+ nodes {
+ id
+ key
+ value
+ variableType
+ protected
+ masked
+ raw
+ environmentScope
+ }
+ }
+ }
+ )
+ end
+
+ context 'when the user is an admin' do
+ let_it_be(:user) { create(:admin) }
+
+ it "returns the instance's CI variables" do
+ variable = create(:ci_instance_variable, key: 'TEST_VAR', value: 'test',
+ masked: false, protected: true, raw: true)
+
+ post_graphql(query, current_user: user)
+
+ expect(graphql_data.dig('ciVariables', 'nodes')).to contain_exactly({
+ 'id' => variable.to_global_id.to_s,
+ 'key' => 'TEST_VAR',
+ 'value' => 'test',
+ 'variableType' => 'ENV_VAR',
+ 'masked' => false,
+ 'protected' => true,
+ 'raw' => true,
+ 'environmentScope' => nil
+ })
+ end
+ end
+
+ context 'when the user is not an admin' do
+ let_it_be(:user) { create(:user) }
+
+ it 'returns nothing' do
+ create(:ci_instance_variable, value: 'verysecret', masked: true)
+
+ post_graphql(query, current_user: user)
+
+ expect(graphql_data.dig('ciVariables')).to be_nil
+ end
+ end
+end
diff --git a/spec/requests/api/graphql/ci/job_spec.rb b/spec/requests/api/graphql/ci/job_spec.rb
index 2fb90dcd92b..3721155c71b 100644
--- a/spec/requests/api/graphql/ci/job_spec.rb
+++ b/spec/requests/api/graphql/ci/job_spec.rb
@@ -13,8 +13,8 @@ RSpec.describe 'Query.project(fullPath).pipelines.job(id)' do
let_it_be(:project) { create(:project, :repository, :public) }
let_it_be(:pipeline) { create(:ci_pipeline, project: project) }
- let_it_be(:prepare_stage) { create(:ci_stage_entity, pipeline: pipeline, project: project, name: 'prepare') }
- let_it_be(:test_stage) { create(:ci_stage_entity, pipeline: pipeline, project: project, name: 'test') }
+ let_it_be(:prepare_stage) { create(:ci_stage, pipeline: pipeline, project: project, name: 'prepare') }
+ let_it_be(:test_stage) { create(:ci_stage, pipeline: pipeline, project: project, name: 'test') }
let_it_be(:job_1) { create(:ci_build, pipeline: pipeline, stage: 'prepare', name: 'Job 1') }
let_it_be(:job_2) { create(:ci_build, pipeline: pipeline, stage: 'test', name: 'Job 2') }
diff --git a/spec/requests/api/graphql/ci/jobs_spec.rb b/spec/requests/api/graphql/ci/jobs_spec.rb
index d1737fc22ae..8c4ab13fc35 100644
--- a/spec/requests/api/graphql/ci/jobs_spec.rb
+++ b/spec/requests/api/graphql/ci/jobs_spec.rb
@@ -14,7 +14,7 @@ RSpec.describe 'Query.project.pipeline' do
describe '.stages.groups.jobs' do
let(:pipeline) do
pipeline = create(:ci_pipeline, project: project, user: user)
- stage = create(:ci_stage_entity, project: project, pipeline: pipeline, name: 'first', position: 1)
+ stage = create(:ci_stage, project: project, pipeline: pipeline, name: 'first', position: 1)
create(:ci_build, stage_id: stage.id, pipeline: pipeline, name: 'my test job', scheduling_type: :stage)
pipeline
@@ -84,8 +84,8 @@ RSpec.describe 'Query.project.pipeline' do
context 'when there is more than one stage and job needs' do
before do
- build_stage = create(:ci_stage_entity, position: 2, name: 'build', project: project, pipeline: pipeline)
- test_stage = create(:ci_stage_entity, position: 3, name: 'test', project: project, pipeline: pipeline)
+ build_stage = create(:ci_stage, position: 2, name: 'build', project: project, pipeline: pipeline)
+ test_stage = create(:ci_stage, position: 3, name: 'test', project: project, pipeline: pipeline)
create(:ci_build, pipeline: pipeline, name: 'docker 1 2', scheduling_type: :stage, stage: build_stage, stage_idx: build_stage.position)
create(:ci_build, pipeline: pipeline, name: 'docker 2 2', stage: build_stage, stage_idx: build_stage.position, scheduling_type: :dag)
diff --git a/spec/requests/api/graphql/ci/manual_variables_spec.rb b/spec/requests/api/graphql/ci/manual_variables_spec.rb
new file mode 100644
index 00000000000..b7aa76511a3
--- /dev/null
+++ b/spec/requests/api/graphql/ci/manual_variables_spec.rb
@@ -0,0 +1,95 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe 'Query.project(fullPath).pipelines.jobs.manualVariables' do
+ include GraphqlHelpers
+
+ let_it_be(:project) { create(:project) }
+ let_it_be(:pipeline) { create(:ci_pipeline, project: project) }
+ let_it_be(:user) { create(:user) }
+
+ let(:query) do
+ %(
+ query {
+ project(fullPath: "#{project.full_path}") {
+ pipelines {
+ nodes {
+ jobs {
+ nodes {
+ manualVariables {
+ nodes {
+ key
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ )
+ end
+
+ before do
+ project.add_maintainer(user)
+ end
+
+ it 'returns the manual variables for the jobs' do
+ job = create(:ci_build, :manual, pipeline: pipeline)
+ create(:ci_job_variable, key: 'MANUAL_TEST_VAR', job: job)
+
+ post_graphql(query, current_user: user)
+
+ variables_data = graphql_data.dig('project', 'pipelines', 'nodes').first
+ .dig('jobs', 'nodes').flat_map { |job| job.dig('manualVariables', 'nodes') }
+ expect(variables_data.map { |var| var['key'] }).to match_array(['MANUAL_TEST_VAR'])
+ end
+
+ it 'does not fetch job variables for jobs that are not manual' do
+ job = create(:ci_build, pipeline: pipeline)
+ create(:ci_job_variable, key: 'THIS_VAR_WOULD_SHOULD_NEVER_EXIST', job: job)
+
+ post_graphql(query, current_user: user)
+
+ variables_data = graphql_data.dig('project', 'pipelines', 'nodes').first
+ .dig('jobs', 'nodes').flat_map { |job| job.dig('manualVariables', 'nodes') }
+ expect(variables_data).to be_empty
+ end
+
+ it 'does not fetch job variables for bridges' do
+ create(:ci_bridge, :manual, pipeline: pipeline)
+
+ post_graphql(query, current_user: user)
+
+ variables_data = graphql_data.dig('project', 'pipelines', 'nodes').first
+ .dig('jobs', 'nodes').flat_map { |job| job.dig('manualVariables', 'nodes') }
+ expect(variables_data).to be_empty
+ end
+
+ it 'does not produce N+1 queries', quarantine: 'https://gitlab.com/gitlab-org/gitlab/-/issues/367991' do
+ second_user = create(:user)
+ project.add_maintainer(second_user)
+ job = create(:ci_build, :manual, pipeline: pipeline)
+ create(:ci_job_variable, key: 'MANUAL_TEST_VAR_1', job: job)
+
+ control_count = ActiveRecord::QueryRecorder.new do
+ post_graphql(query, current_user: user)
+ end
+
+ variables_data = graphql_data.dig('project', 'pipelines', 'nodes').first
+ .dig('jobs', 'nodes').flat_map { |job| job.dig('manualVariables', 'nodes') }
+ expect(variables_data.map { |var| var['key'] }).to match_array(['MANUAL_TEST_VAR_1'])
+
+ job = create(:ci_build, :manual, pipeline: pipeline)
+ create(:ci_job_variable, key: 'MANUAL_TEST_VAR_2', job: job)
+
+ expect do
+ post_graphql(query, current_user: second_user)
+ end.not_to exceed_query_limit(control_count)
+
+ variables_data = graphql_data.dig('project', 'pipelines', 'nodes').first
+ .dig('jobs', 'nodes').flat_map { |job| job.dig('manualVariables', 'nodes') }
+ expect(variables_data.map { |var| var['key'] }).to match_array(%w(MANUAL_TEST_VAR_1 MANUAL_TEST_VAR_2))
+ end
+end
diff --git a/spec/requests/api/graphql/ci/pipelines_spec.rb b/spec/requests/api/graphql/ci/pipelines_spec.rb
index 741af676b6d..a968e5508cb 100644
--- a/spec/requests/api/graphql/ci/pipelines_spec.rb
+++ b/spec/requests/api/graphql/ci/pipelines_spec.rb
@@ -86,8 +86,8 @@ RSpec.describe 'Query.project(fullPath).pipelines' do
describe '.stages' do
let_it_be(:project) { create(:project, :repository) }
let_it_be(:pipeline) { create(:ci_empty_pipeline, project: project) }
- let_it_be(:stage) { create(:ci_stage_entity, pipeline: pipeline, project: project) }
- let_it_be(:other_stage) { create(:ci_stage_entity, pipeline: pipeline, project: project, name: 'other') }
+ let_it_be(:stage) { create(:ci_stage, pipeline: pipeline, project: project) }
+ let_it_be(:other_stage) { create(:ci_stage, pipeline: pipeline, project: project, name: 'other') }
let(:first_n) { var('Int') }
let(:query_path) do
diff --git a/spec/requests/api/graphql/ci/project_variables_spec.rb b/spec/requests/api/graphql/ci/project_variables_spec.rb
new file mode 100644
index 00000000000..e61f146b24c
--- /dev/null
+++ b/spec/requests/api/graphql/ci/project_variables_spec.rb
@@ -0,0 +1,67 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe 'Query.project(fullPath).ciVariables' do
+ include GraphqlHelpers
+
+ let_it_be(:project) { create(:project) }
+ let_it_be(:user) { create(:user) }
+
+ let(:query) do
+ %(
+ query {
+ project(fullPath: "#{project.full_path}") {
+ ciVariables {
+ nodes {
+ id
+ key
+ value
+ variableType
+ protected
+ masked
+ raw
+ environmentScope
+ }
+ }
+ }
+ }
+ )
+ end
+
+ context 'when the user can administer builds' do
+ before do
+ project.add_maintainer(user)
+ end
+
+ 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')
+
+ post_graphql(query, current_user: user)
+
+ expect(graphql_data.dig('project', 'ciVariables', 'nodes')).to contain_exactly({
+ 'id' => variable.to_global_id.to_s,
+ 'key' => 'TEST_VAR',
+ 'value' => 'test',
+ 'variableType' => 'ENV_VAR',
+ 'masked' => false,
+ 'protected' => true,
+ 'raw' => true,
+ 'environmentScope' => 'production'
+ })
+ end
+ end
+
+ context 'when the user cannot administer builds' do
+ it 'returns nothing' do
+ create(:ci_variable, project: project, value: 'verysecret', masked: true)
+
+ project.add_developer(user)
+
+ post_graphql(query, current_user: user)
+
+ expect(graphql_data.dig('project', 'ciVariables')).to be_nil
+ end
+ end
+end
diff --git a/spec/requests/api/graphql/ci/runner_spec.rb b/spec/requests/api/graphql/ci/runner_spec.rb
index 446d1fb1bdb..e17a83d8e47 100644
--- a/spec/requests/api/graphql/ci/runner_spec.rb
+++ b/spec/requests/api/graphql/ci/runner_spec.rb
@@ -424,7 +424,7 @@ RSpec.describe 'Query.runner(id)' do
let(:user) { create(:user) }
before do
- group.add_user(user, Gitlab::Access::OWNER)
+ group.add_member(user, Gitlab::Access::OWNER)
end
it_behaves_like 'retrieval with no admin url' do
diff --git a/spec/requests/api/graphql/ci/stages_spec.rb b/spec/requests/api/graphql/ci/stages_spec.rb
index 50d2cf75097..1edd6e58486 100644
--- a/spec/requests/api/graphql/ci/stages_spec.rb
+++ b/spec/requests/api/graphql/ci/stages_spec.rb
@@ -36,7 +36,7 @@ RSpec.describe 'Query.project.pipeline.stages' do
end
before_all do
- create(:ci_stage_entity, pipeline: pipeline, name: 'deploy')
+ create(:ci_stage, pipeline: pipeline, name: 'deploy')
create_list(:ci_build, 2, pipeline: pipeline, stage: 'deploy')
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
index 847fa72522e..14c55e61a65 100644
--- a/spec/requests/api/graphql/container_repository/container_repository_details_spec.rb
+++ b/spec/requests/api/graphql/container_repository/container_repository_details_spec.rb
@@ -71,7 +71,7 @@ RSpec.describe 'container repository details' do
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
+ project.add_member(user, role) unless role == :anonymous
end
it 'return the proper response' do
diff --git a/spec/requests/api/graphql/crm/contacts_spec.rb b/spec/requests/api/graphql/crm/contacts_spec.rb
new file mode 100644
index 00000000000..7e824140894
--- /dev/null
+++ b/spec/requests/api/graphql/crm/contacts_spec.rb
@@ -0,0 +1,69 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe 'getting CRM contacts' do
+ include GraphqlHelpers
+
+ let_it_be(:current_user) { create(:user) }
+ let_it_be(:group) { create(:group, :crm_enabled) }
+
+ let_it_be(:contact_a) do
+ create(
+ :contact,
+ group: group,
+ first_name: "ABC",
+ last_name: "DEF",
+ email: "ghi@test.com",
+ description: "LMNO",
+ state: "inactive"
+ )
+ end
+
+ let_it_be(:contact_b) do
+ create(
+ :contact,
+ group: group,
+ first_name: "ABC",
+ last_name: "DEF",
+ email: "vwx@test.com",
+ description: "YZ",
+ state: "active"
+ )
+ end
+
+ let_it_be(:contact_c) do
+ create(
+ :contact,
+ group: group,
+ first_name: "PQR",
+ last_name: "STU",
+ email: "aaa@test.com",
+ description: "YZ",
+ state: "active"
+ )
+ end
+
+ before do
+ group.add_reporter(current_user)
+ end
+
+ it_behaves_like 'sorted paginated query' do
+ let(:sort_argument) { {} }
+ let(:first_param) { 2 }
+ let(:all_records) { [contact_a, contact_b, contact_c] }
+ let(:data_path) { [:group, :contacts] }
+
+ def pagination_query(params)
+ graphql_query_for(
+ :group,
+ { full_path: group.full_path },
+ query_graphql_field(:contacts, params, "#{page_info} nodes { id }")
+ )
+ end
+
+ def pagination_results_data(nodes)
+ nodes.map { |item| GlobalID::Locator.locate(item['id']) }
+ end
+ end
+end
diff --git a/spec/requests/api/graphql/current_user/groups_query_spec.rb b/spec/requests/api/graphql/current_user/groups_query_spec.rb
index 39f323b21a3..ef0f32bacf0 100644
--- a/spec/requests/api/graphql/current_user/groups_query_spec.rb
+++ b/spec/requests/api/graphql/current_user/groups_query_spec.rb
@@ -8,8 +8,9 @@ RSpec.describe 'Query current user groups' do
let_it_be(:user) { create(:user) }
let_it_be(:guest_group) { create(:group, name: 'public guest', path: 'public-guest') }
let_it_be(:private_maintainer_group) { create(:group, :private, name: 'b private maintainer', path: 'b-private-maintainer') }
- let_it_be(:public_developer_group) { create(:group, :private, project_creation_level: nil, name: 'c public developer', path: 'c-public-developer') }
- let_it_be(:public_maintainer_group) { create(:group, :private, name: 'a public maintainer', path: 'a-public-maintainer') }
+ let_it_be(:public_developer_group) { create(:group, project_creation_level: nil, name: 'c public developer', path: 'c-public-developer') }
+ let_it_be(:public_maintainer_group) { create(:group, name: 'a public maintainer', path: 'a-public-maintainer') }
+ let_it_be(:public_owner_group) { create(:group, name: 'a public owner', path: 'a-public-owner') }
let(:group_arguments) { {} }
let(:current_user) { user }
@@ -29,6 +30,7 @@ RSpec.describe 'Query current user groups' do
private_maintainer_group.add_maintainer(user)
public_developer_group.add_developer(user)
public_maintainer_group.add_maintainer(user)
+ public_owner_group.add_owner(user)
end
subject { graphql_data.dig('currentUser', 'groups', 'nodes') }
@@ -52,6 +54,7 @@ RSpec.describe 'Query current user groups' do
is_expected.to match(
expected_group_hash(
public_maintainer_group,
+ public_owner_group,
private_maintainer_group,
public_developer_group,
guest_group
@@ -66,6 +69,7 @@ RSpec.describe 'Query current user groups' do
is_expected.to match(
expected_group_hash(
public_maintainer_group,
+ public_owner_group,
private_maintainer_group,
public_developer_group
)
@@ -86,6 +90,32 @@ RSpec.describe 'Query current user groups' do
end
end
+ context 'when permission_scope is TRANSFER_PROJECTS' do
+ let(:group_arguments) { { permission_scope: :TRANSFER_PROJECTS } }
+
+ specify do
+ is_expected.to match(
+ expected_group_hash(
+ public_maintainer_group,
+ public_owner_group,
+ private_maintainer_group
+ )
+ )
+ end
+
+ context 'when search is provided' do
+ let(:group_arguments) { { permission_scope: :TRANSFER_PROJECTS, search: 'owner' } }
+
+ specify do
+ is_expected.to match(
+ expected_group_hash(
+ public_owner_group
+ )
+ )
+ end
+ end
+ end
+
context 'when search is provided' do
let(:group_arguments) { { search: 'maintainer' } }
diff --git a/spec/requests/api/graphql/group/container_repositories_spec.rb b/spec/requests/api/graphql/group/container_repositories_spec.rb
index be0b866af4a..8ec321c8d7c 100644
--- a/spec/requests/api/graphql/group/container_repositories_spec.rb
+++ b/spec/requests/api/graphql/group/container_repositories_spec.rb
@@ -82,7 +82,7 @@ RSpec.describe 'getting container repositories in a group' 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
+ group.add_member(user, role) unless role == :anonymous
end
it 'return the proper response' do
diff --git a/spec/requests/api/graphql/group/dependency_proxy_blobs_spec.rb b/spec/requests/api/graphql/group/dependency_proxy_blobs_spec.rb
index cdb21512894..daa1483e956 100644
--- a/spec/requests/api/graphql/group/dependency_proxy_blobs_spec.rb
+++ b/spec/requests/api/graphql/group/dependency_proxy_blobs_spec.rb
@@ -75,7 +75,7 @@ RSpec.describe 'getting dependency proxy blobs in a group' do
with_them do
before do
group.update_column(:visibility_level, Gitlab::VisibilityLevel.const_get(group_visibility.to_s.upcase, false))
- group.add_user(user, role) unless role == :anonymous
+ group.add_member(user, role) unless role == :anonymous
end
it 'return the proper response' do
diff --git a/spec/requests/api/graphql/group/dependency_proxy_group_setting_spec.rb b/spec/requests/api/graphql/group/dependency_proxy_group_setting_spec.rb
index d21c3046c1a..cc706c3051f 100644
--- a/spec/requests/api/graphql/group/dependency_proxy_group_setting_spec.rb
+++ b/spec/requests/api/graphql/group/dependency_proxy_group_setting_spec.rb
@@ -61,7 +61,7 @@ RSpec.describe 'getting dependency proxy settings for a group' do
with_them do
before do
group.update_column(:visibility_level, Gitlab::VisibilityLevel.const_get(group_visibility.to_s.upcase, false))
- group.add_user(user, role) unless role == :anonymous
+ group.add_member(user, role) unless role == :anonymous
end
it 'return the proper response' do
diff --git a/spec/requests/api/graphql/group/dependency_proxy_image_ttl_policy_spec.rb b/spec/requests/api/graphql/group/dependency_proxy_image_ttl_policy_spec.rb
index 40f4b082072..3b2b04b1322 100644
--- a/spec/requests/api/graphql/group/dependency_proxy_image_ttl_policy_spec.rb
+++ b/spec/requests/api/graphql/group/dependency_proxy_image_ttl_policy_spec.rb
@@ -60,7 +60,7 @@ RSpec.describe 'getting dependency proxy image ttl policy for a group' do
with_them do
before do
group.update_column(:visibility_level, Gitlab::VisibilityLevel.const_get(group_visibility.to_s.upcase, false))
- group.add_user(user, role) unless role == :anonymous
+ group.add_member(user, role) unless role == :anonymous
end
it 'return the proper response' do
diff --git a/spec/requests/api/graphql/group/dependency_proxy_manifests_spec.rb b/spec/requests/api/graphql/group/dependency_proxy_manifests_spec.rb
index c7149c100b2..37ef7089c2f 100644
--- a/spec/requests/api/graphql/group/dependency_proxy_manifests_spec.rb
+++ b/spec/requests/api/graphql/group/dependency_proxy_manifests_spec.rb
@@ -73,7 +73,7 @@ RSpec.describe 'getting dependency proxy manifests in a group' do
with_them do
before do
group.update_column(:visibility_level, Gitlab::VisibilityLevel.const_get(group_visibility.to_s.upcase, false))
- group.add_user(user, role) unless role == :anonymous
+ group.add_member(user, role) unless role == :anonymous
end
it 'return the proper response' do
@@ -125,7 +125,8 @@ RSpec.describe 'getting dependency proxy manifests in a group' do
let_it_be(:descending_manifests) { manifests.reverse.map { |manifest| global_id_of(manifest) } }
it_behaves_like 'sorted paginated query' do
- let(:sort_param) { '' }
+ include_context 'no sort argument'
+
let(:first_param) { 2 }
let(:all_records) { descending_manifests.map(&:to_s) }
end
@@ -134,7 +135,7 @@ RSpec.describe 'getting dependency proxy manifests in a group' do
def pagination_query(params)
# remove sort since the type does not accept sorting, but be future proof
graphql_query_for('group', { 'fullPath' => group.full_path },
- query_nodes(:dependencyProxyManifests, :id, include_pagination_info: true, args: params.merge(sort: nil))
+ query_nodes(:dependencyProxyManifests, :id, include_pagination_info: true, args: params)
)
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 fec866486ae..1ff5b134e92 100644
--- a/spec/requests/api/graphql/group/group_members_spec.rb
+++ b/spec/requests/api/graphql/group/group_members_spec.rb
@@ -7,8 +7,8 @@ RSpec.describe 'getting group members information' do
let_it_be(:parent_group) { create(:group, :public) }
let_it_be(:user) { create(:user) }
- let_it_be(:user_1) { create(:user, username: 'user') }
- let_it_be(:user_2) { create(:user, username: 'test') }
+ let_it_be(:user_1) { create(:user, username: 'user', name: 'Same Name') }
+ let_it_be(:user_2) { create(:user, username: 'test', name: 'Same Name') }
before_all do
[user_1, user_2].each { |user| parent_group.add_guest(user) }
@@ -45,11 +45,44 @@ RSpec.describe 'getting group members information' do
expect_array_response(user_1, user_2)
end
- it 'returns members that match the search query' do
- fetch_members(args: { search: 'test' })
+ describe 'search argument' do
+ it 'returns members that match the search query' do
+ fetch_members(args: { search: 'test' })
- expect(graphql_errors).to be_nil
- expect_array_response(user_2)
+ expect(graphql_errors).to be_nil
+ expect_array_response(user_2)
+ end
+
+ context 'when paginating' do
+ it 'returns correct results' do
+ fetch_members(args: { search: 'Same Name', first: 1 })
+
+ expect_array_response(user_1)
+
+ next_cursor = graphql_data_at(:group, :groupMembers, :pageInfo, :endCursor)
+ fetch_members(args: { search: 'Same Name', first: 1, after: next_cursor })
+
+ expect_array_response(user_2)
+ end
+
+ context 'when the use_keyset_aware_user_search_query FF is off' do
+ before do
+ stub_feature_flags(use_keyset_aware_user_search_query: false)
+ end
+
+ it 'raises error on the 2nd page due to missing cursor data' do
+ fetch_members(args: { search: 'Same Name', first: 1 })
+
+ # user_2 because the "old" order was undeterministic (insert order), no tie-breaker column
+ expect_array_response(user_2)
+
+ next_cursor = graphql_data_at(:group, :groupMembers, :pageInfo, :endCursor)
+ fetch_members(args: { search: 'Same Name', first: 1, after: next_cursor })
+
+ expect(graphql_errors.first['message']).to include('PG::UndefinedColumn')
+ end
+ end
+ end
end
end
@@ -196,6 +229,9 @@ RSpec.describe 'getting group members information' do
}
}
}
+ pageInfo {
+ endCursor
+ }
NODE
graphql_query_for("group",
diff --git a/spec/requests/api/graphql/mutations/issues/create_spec.rb b/spec/requests/api/graphql/mutations/issues/create_spec.rb
index 3d81b456c9c..9345735afe4 100644
--- a/spec/requests/api/graphql/mutations/issues/create_spec.rb
+++ b/spec/requests/api/graphql/mutations/issues/create_spec.rb
@@ -53,6 +53,42 @@ RSpec.describe 'Create an issue' do
let(:mutation_class) { ::Mutations::Issues::Create }
end
+ context 'when creating an issue of type TASK' do
+ before do
+ input['type'] = 'TASK'
+ end
+
+ context 'when work_items feature flag is disabled' do
+ before do
+ stub_feature_flags(work_items: false)
+ end
+
+ it 'creates an issue with the default ISSUE type' do
+ expect do
+ post_graphql_mutation(mutation, current_user: current_user)
+ end.to change(Issue, :count).by(1)
+
+ created_issue = Issue.last
+
+ expect(created_issue.work_item_type.base_type).to eq('issue')
+ expect(created_issue.issue_type).to eq('issue')
+ end
+ end
+
+ context 'when work_items feature flag is enabled' do
+ it 'creates an issue with TASK type' do
+ expect do
+ post_graphql_mutation(mutation, current_user: current_user)
+ end.to change(Issue, :count).by(1)
+
+ created_issue = Issue.last
+
+ expect(created_issue.work_item_type.base_type).to eq('task')
+ expect(created_issue.issue_type).to eq('task')
+ end
+ end
+ end
+
context 'when position params are provided' do
let(:existing_issue) { create(:issue, project: project, relative_position: 50) }
diff --git a/spec/requests/api/graphql/mutations/notes/create/diff_note_spec.rb b/spec/requests/api/graphql/mutations/notes/create/diff_note_spec.rb
index 8f3ae9f26f6..a432fb17a70 100644
--- a/spec/requests/api/graphql/mutations/notes/create/diff_note_spec.rb
+++ b/spec/requests/api/graphql/mutations/notes/create/diff_note_spec.rb
@@ -10,11 +10,12 @@ RSpec.describe 'Adding a DiffNote' do
let(:noteable) { create(:merge_request, source_project: project, target_project: project) }
let(:project) { create(:project, :repository) }
let(:diff_refs) { noteable.diff_refs }
+ let(:body) { 'Body text' }
let(:base_variables) do
{
noteable_id: GitlabSchema.id_from_object(noteable).to_s,
- body: 'Body text',
+ body: body,
position: {
paths: {
old_path: 'files/ruby/popen.rb',
@@ -65,6 +66,17 @@ RSpec.describe 'Adding a DiffNote' do
it_behaves_like 'a Note mutation when the given resource id is not for a Noteable'
end
+ context 'with /merge quick action' do
+ let(:body) { "Body text \n/merge" }
+
+ it 'merges the merge request', :sidekiq_inline do
+ post_graphql_mutation(mutation, current_user: current_user)
+
+ expect(noteable.reload.state).to eq('merged')
+ expect(mutation_response['note']['body']).to eq('Body text')
+ end
+ end
+
it 'returns the note with the correct position' do
post_graphql_mutation(mutation, current_user: current_user)
diff --git a/spec/requests/api/graphql/mutations/snippets/update_spec.rb b/spec/requests/api/graphql/mutations/snippets/update_spec.rb
index eb7e6f840fe..1a5d3620f22 100644
--- a/spec/requests/api/graphql/mutations/snippets/update_spec.rb
+++ b/spec/requests/api/graphql/mutations/snippets/update_spec.rb
@@ -4,6 +4,7 @@ require 'spec_helper'
RSpec.describe 'Updating a Snippet' do
include GraphqlHelpers
+ include SessionHelpers
let_it_be(:original_content) { 'Initial content' }
let_it_be(:original_description) { 'Initial description' }
@@ -162,7 +163,7 @@ RSpec.describe 'Updating a Snippet' do
end
end
- context 'when the author is a member of the project' do
+ context 'when the author is a member of the project', :snowplow do
before do
project.add_developer(current_user)
end
@@ -185,6 +186,20 @@ RSpec.describe 'Updating a Snippet' do
it_behaves_like 'has spam protection' do
let(:mutation_class) { ::Mutations::Snippets::Update }
end
+
+ context 'when not sessionless', :clean_gitlab_redis_sessions do
+ before do
+ stub_session('warden.user.user.key' => [[current_user.id], current_user.authenticatable_salt])
+ end
+
+ it_behaves_like 'Snowplow event tracking' do
+ let(:user) { current_user }
+ let(:namespace) { project.namespace }
+ let(:category) { 'ide_edit' }
+ let(:action) { 'g_edit_by_snippet_ide' }
+ let(:feature_flag_name) { :route_hll_to_snowplow_phase2 }
+ end
+ end
end
it_behaves_like 'when the snippet is not found'
diff --git a/spec/requests/api/graphql/mutations/work_items/create_from_task_spec.rb b/spec/requests/api/graphql/mutations/work_items/create_from_task_spec.rb
index 8d33f8e1806..b1356bbe6fd 100644
--- a/spec/requests/api/graphql/mutations/work_items/create_from_task_spec.rb
+++ b/spec/requests/api/graphql/mutations/work_items/create_from_task_spec.rb
@@ -47,6 +47,7 @@ RSpec.describe "Create a work item from a task in a work item's description" do
expect(work_item.description).to eq("- [ ] #{created_work_item.to_reference}+")
expect(created_work_item.issue_type).to eq('task')
expect(created_work_item.work_item_type.base_type).to eq('task')
+ expect(created_work_item.work_item_parent).to eq(work_item)
expect(mutation_response['workItem']).to include('id' => work_item.to_global_id.to_s)
expect(mutation_response['newWorkItem']).to include('id' => created_work_item.to_global_id.to_s)
end
diff --git a/spec/requests/api/graphql/mutations/work_items/create_spec.rb b/spec/requests/api/graphql/mutations/work_items/create_spec.rb
index 6abdaa2c850..911568bc39f 100644
--- a/spec/requests/api/graphql/mutations/work_items/create_spec.rb
+++ b/spec/requests/api/graphql/mutations/work_items/create_spec.rb
@@ -63,6 +63,95 @@ RSpec.describe 'Create a work item' do
let(:mutation_class) { ::Mutations::WorkItems::Create }
end
+ context 'with hierarchy widget input' do
+ let(:widgets_response) { mutation_response['workItem']['widgets'] }
+ let(:fields) do
+ <<~FIELDS
+ workItem {
+ widgets {
+ type
+ ... on WorkItemWidgetHierarchy {
+ parent {
+ id
+ }
+ children {
+ edges {
+ node {
+ id
+ }
+ }
+ }
+ }
+ }
+ }
+ errors
+ FIELDS
+ end
+
+ let(:mutation) { graphql_mutation(:workItemCreate, input.merge('projectPath' => project.full_path), fields) }
+
+ context 'when setting parent' do
+ let_it_be(:parent) { create(:work_item, project: project) }
+
+ let(:input) do
+ {
+ title: 'item1',
+ workItemTypeId: WorkItems::Type.default_by_type(:task).to_global_id.to_s,
+ hierarchyWidget: { 'parentId' => parent.to_global_id.to_s }
+ }
+ end
+
+ it 'updates the work item parent' do
+ post_graphql_mutation(mutation, current_user: current_user)
+
+ expect(response).to have_gitlab_http_status(:success)
+ expect(widgets_response).to include(
+ {
+ 'children' => { 'edges' => [] },
+ 'parent' => { 'id' => parent.to_global_id.to_s },
+ 'type' => 'HIERARCHY'
+ }
+ )
+ end
+
+ context 'when parent work item type is invalid' do
+ let_it_be(:parent) { create(:work_item, :task, project: project) }
+
+ it 'returns error' do
+ post_graphql_mutation(mutation, current_user: current_user)
+
+ expect(mutation_response['errors'])
+ .to contain_exactly(/cannot be added: only Issue and Incident can be parent of Task./)
+ expect(mutation_response['workItem']).to be_nil
+ end
+ end
+
+ context 'when parent work item is not found' do
+ let_it_be(:parent) { build_stubbed(:work_item, id: non_existing_record_id)}
+
+ it 'returns a top level error' do
+ post_graphql_mutation(mutation, current_user: current_user)
+
+ expect(graphql_errors.first['message']).to include('No object found for `parentId')
+ end
+ end
+ end
+
+ context 'when unsupported widget input is sent' do
+ let(:input) do
+ {
+ 'title' => 'new title',
+ 'description' => 'new description',
+ 'workItemTypeId' => WorkItems::Type.default_by_type(:test_case).to_global_id.to_s,
+ 'hierarchyWidget' => {}
+ }
+ end
+
+ it_behaves_like 'a mutation that returns top-level errors',
+ errors: ['Following widget keys are not supported by Test Case type: [:hierarchy_widget]']
+ end
+ end
+
context 'when the work_items feature flag is disabled' do
before do
stub_feature_flags(work_items: false)
diff --git a/spec/requests/api/graphql/mutations/work_items/delete_task_spec.rb b/spec/requests/api/graphql/mutations/work_items/delete_task_spec.rb
index 05d3587d342..e576d0ee7ef 100644
--- a/spec/requests/api/graphql/mutations/work_items/delete_task_spec.rb
+++ b/spec/requests/api/graphql/mutations/work_items/delete_task_spec.rb
@@ -54,7 +54,7 @@ RSpec.describe "Delete a task in a work item's description" do
end.to change(WorkItem, :count).by(-1).and(
change(IssueLink, :count).by(-1)
).and(
- change(work_item, :description).from("- [ ] #{task.to_reference}+").to('')
+ change(work_item, :description).from("- [ ] #{task.to_reference}+").to("- [ ] #{task.title}")
)
expect(response).to have_gitlab_http_status(:success)
diff --git a/spec/requests/api/graphql/mutations/work_items/update_spec.rb b/spec/requests/api/graphql/mutations/work_items/update_spec.rb
index 71b03103115..77f7b9bacef 100644
--- a/spec/requests/api/graphql/mutations/work_items/update_spec.rb
+++ b/spec/requests/api/graphql/mutations/work_items/update_spec.rb
@@ -11,8 +11,17 @@ RSpec.describe 'Update a work item' do
let(:work_item_event) { 'CLOSE' }
let(:input) { { 'stateEvent' => work_item_event, 'title' => 'updated title' } }
+ let(:fields) do
+ <<~FIELDS
+ workItem {
+ state
+ title
+ }
+ errors
+ FIELDS
+ end
- let(:mutation) { graphql_mutation(:workItemUpdate, input.merge('id' => work_item.to_global_id.to_s)) }
+ let(:mutation) { graphql_mutation(:workItemUpdate, input.merge('id' => work_item.to_global_id.to_s), fields) }
let(:mutation_response) { graphql_mutation_response(:work_item_update) }
@@ -62,6 +71,20 @@ RSpec.describe 'Update a work item' do
end
end
+ context 'when unsupported widget input is sent' do
+ let_it_be(:test_case) { create(:work_item_type, :default, :test_case, name: 'some_test_case_name') }
+ let_it_be(:work_item) { create(:work_item, work_item_type: test_case, project: project) }
+
+ let(:input) do
+ {
+ 'hierarchyWidget' => {}
+ }
+ end
+
+ it_behaves_like 'a mutation that returns top-level errors',
+ errors: ["Following widget keys are not supported by some_test_case_name type: [:hierarchy_widget]"]
+ end
+
it_behaves_like 'has spam protection' do
let(:mutation_class) { ::Mutations::WorkItems::Update }
end
@@ -80,5 +103,248 @@ RSpec.describe 'Update a work item' do
expect(mutation_response['errors']).to contain_exactly('`work_items` feature flag disabled for this project')
end
end
+
+ context 'with description widget input' do
+ let(:fields) do
+ <<~FIELDS
+ workItem {
+ description
+ widgets {
+ type
+ ... on WorkItemWidgetDescription {
+ description
+ }
+ }
+ }
+ errors
+ FIELDS
+ end
+
+ it_behaves_like 'update work item description widget' do
+ let(:new_description) { 'updated description' }
+ let(:input) do
+ { 'descriptionWidget' => { 'description' => new_description } }
+ end
+ end
+ end
+
+ context 'with weight widget input' do
+ let(:fields) do
+ <<~FIELDS
+ workItem {
+ widgets {
+ type
+ ... on WorkItemWidgetWeight {
+ weight
+ }
+ }
+ }
+ errors
+ FIELDS
+ end
+
+ it_behaves_like 'update work item weight widget' do
+ let(:new_weight) { 2 }
+
+ let(:input) do
+ { 'weightWidget' => { 'weight' => new_weight } }
+ end
+ end
+ end
+
+ context 'with hierarchy widget input' do
+ let(:widgets_response) { mutation_response['workItem']['widgets'] }
+ let(:fields) do
+ <<~FIELDS
+ workItem {
+ description
+ widgets {
+ type
+ ... on WorkItemWidgetHierarchy {
+ parent {
+ id
+ }
+ children {
+ edges {
+ node {
+ id
+ }
+ }
+ }
+ }
+ }
+ }
+ errors
+ FIELDS
+ end
+
+ context 'when updating parent' do
+ let_it_be(:work_item) { create(:work_item, :task, project: project) }
+ let_it_be(:valid_parent) { create(:work_item, project: project) }
+ let_it_be(:invalid_parent) { create(:work_item, :task, project: project) }
+
+ context 'when parent work item type is invalid' do
+ let(:error) { "#{work_item.to_reference} cannot be added: only Issue and Incident can be parent of Task." }
+ let(:input) do
+ { 'hierarchyWidget' => { 'parentId' => invalid_parent.to_global_id.to_s }, 'title' => 'new title' }
+ end
+
+ it 'returns response with errors' do
+ expect do
+ post_graphql_mutation(mutation, current_user: current_user)
+ work_item.reload
+ end.to not_change(work_item, :work_item_parent).and(not_change(work_item, :title))
+
+ expect(mutation_response['workItem']).to be_nil
+ expect(mutation_response['errors']).to match_array([error])
+ end
+ end
+
+ context 'when parent work item has a valid type' do
+ let(:input) { { 'hierarchyWidget' => { 'parentId' => valid_parent.to_global_id.to_s } } }
+
+ it 'sets the parent for the work item' do
+ expect do
+ post_graphql_mutation(mutation, current_user: current_user)
+ work_item.reload
+ end.to change(work_item, :work_item_parent).from(nil).to(valid_parent)
+
+ expect(response).to have_gitlab_http_status(:success)
+ expect(widgets_response).to include(
+ {
+ 'children' => { 'edges' => [] },
+ 'parent' => { 'id' => valid_parent.to_global_id.to_s },
+ 'type' => 'HIERARCHY'
+ }
+ )
+ end
+
+ context 'when a parent is already present' do
+ let_it_be(:existing_parent) { create(:work_item, project: project) }
+
+ before do
+ work_item.update!(work_item_parent: existing_parent)
+ end
+
+ it 'is replaced with new parent' do
+ expect do
+ post_graphql_mutation(mutation, current_user: current_user)
+ work_item.reload
+ end.to change(work_item, :work_item_parent).from(existing_parent).to(valid_parent)
+ end
+ end
+ end
+
+ context 'when parentId is null' do
+ let(:input) { { 'hierarchyWidget' => { 'parentId' => nil } } }
+
+ context 'when parent is present' do
+ before do
+ work_item.update!(work_item_parent: valid_parent)
+ end
+
+ it 'removes parent and returns success message' do
+ expect do
+ post_graphql_mutation(mutation, current_user: current_user)
+ work_item.reload
+ end.to change(work_item, :work_item_parent).from(valid_parent).to(nil)
+
+ expect(response).to have_gitlab_http_status(:success)
+ expect(widgets_response)
+ .to include(
+ {
+ 'children' => { 'edges' => [] },
+ 'parent' => nil,
+ 'type' => 'HIERARCHY'
+ }
+ )
+ end
+ end
+
+ context 'when parent is not present' do
+ before do
+ work_item.update!(work_item_parent: nil)
+ end
+
+ it 'does not change work item and returns success message' do
+ expect do
+ post_graphql_mutation(mutation, current_user: current_user)
+ work_item.reload
+ end.not_to change(work_item, :work_item_parent)
+
+ expect(response).to have_gitlab_http_status(:success)
+ end
+ end
+ end
+
+ context 'when parent work item is not found' do
+ let(:input) { { 'hierarchyWidget' => { 'parentId' => "gid://gitlab/WorkItem/#{non_existing_record_id}" } } }
+
+ it 'returns a top level error' do
+ post_graphql_mutation(mutation, current_user: current_user)
+
+ expect(graphql_errors.first['message']).to include('No object found for `parentId')
+ end
+ end
+ end
+
+ context 'when updating children' do
+ let_it_be(:valid_child1) { create(:work_item, :task, project: project) }
+ let_it_be(:valid_child2) { create(:work_item, :task, project: project) }
+ let_it_be(:invalid_child) { create(:work_item, project: project) }
+
+ let(:input) { { 'hierarchyWidget' => { 'childrenIds' => children_ids } } }
+ let(:error) do
+ "#{invalid_child.to_reference} cannot be added: only Task can be assigned as a child in hierarchy."
+ end
+
+ context 'when child work item type is invalid' do
+ let(:children_ids) { [invalid_child.to_global_id.to_s] }
+
+ it 'returns response with errors' do
+ post_graphql_mutation(mutation, current_user: current_user)
+
+ expect(mutation_response['workItem']).to be_nil
+ expect(mutation_response['errors']).to match_array([error])
+ end
+ end
+
+ context 'when there is a mix of existing and non existing work items' do
+ let(:children_ids) { [valid_child1.to_global_id.to_s, "gid://gitlab/WorkItem/#{non_existing_record_id}"] }
+
+ it 'returns a top level error and does not add valid work item' do
+ expect do
+ post_graphql_mutation(mutation, current_user: current_user)
+ work_item.reload
+ end.not_to change(work_item.work_item_children, :count)
+
+ expect(graphql_errors.first['message']).to include('No object found for `childrenIds')
+ end
+ end
+
+ context 'when child work item type is valid' do
+ let(:children_ids) { [valid_child1.to_global_id.to_s, valid_child2.to_global_id.to_s] }
+
+ it 'updates the work item children' do
+ expect do
+ post_graphql_mutation(mutation, current_user: current_user)
+ work_item.reload
+ end.to change(work_item.work_item_children, :count).by(2)
+
+ expect(response).to have_gitlab_http_status(:success)
+ expect(widgets_response).to include(
+ {
+ 'children' => { 'edges' => [
+ { 'node' => { 'id' => valid_child2.to_global_id.to_s } },
+ { 'node' => { 'id' => valid_child1.to_global_id.to_s } }
+ ] },
+ 'parent' => nil,
+ 'type' => 'HIERARCHY'
+ }
+ )
+ end
+ end
+ end
+ end
end
end
diff --git a/spec/requests/api/graphql/mutations/work_items/update_widgets_spec.rb b/spec/requests/api/graphql/mutations/work_items/update_widgets_spec.rb
index 595d8fe97ed..2a5cb937a2f 100644
--- a/spec/requests/api/graphql/mutations/work_items/update_widgets_spec.rb
+++ b/spec/requests/api/graphql/mutations/work_items/update_widgets_spec.rb
@@ -9,16 +9,23 @@ RSpec.describe 'Update work item widgets' do
let_it_be(:developer) { create(:user).tap { |user| project.add_developer(user) } }
let_it_be(:work_item, refind: true) { create(:work_item, project: project) }
- let(:input) do
- {
- 'descriptionWidget' => { 'description' => 'updated description' }
+ let(:input) { { 'descriptionWidget' => { 'description' => 'updated description' } } }
+ let(:mutation_response) { graphql_mutation_response(:work_item_update_widgets) }
+ let(:mutation) do
+ graphql_mutation(:workItemUpdateWidgets, input.merge('id' => work_item.to_global_id.to_s), <<~FIELDS)
+ errors
+ workItem {
+ description
+ widgets {
+ type
+ ... on WorkItemWidgetDescription {
+ description
+ }
+ }
}
+ FIELDS
end
- let(:mutation) { graphql_mutation(:workItemUpdateWidgets, input.merge('id' => work_item.to_global_id.to_s)) }
-
- let(:mutation_response) { graphql_mutation_response(:work_item_update_widgets) }
-
context 'the user is not allowed to update a work item' do
let(:current_user) { create(:user) }
@@ -28,32 +35,8 @@ RSpec.describe 'Update work item widgets' do
context 'when user has permissions to update a work item', :aggregate_failures do
let(:current_user) { developer }
- context 'when the updated work item is not valid' do
- it 'returns validation errors without the work item' do
- errors = ActiveModel::Errors.new(work_item).tap { |e| e.add(:description, 'error message') }
-
- allow_next_found_instance_of(::WorkItem) do |instance|
- allow(instance).to receive(:valid?).and_return(false)
- allow(instance).to receive(:errors).and_return(errors)
- end
-
- post_graphql_mutation(mutation, current_user: current_user)
-
- expect(mutation_response['workItem']).to be_nil
- expect(mutation_response['errors']).to match_array(['Description error message'])
- end
- end
-
- it 'updates the work item widgets' do
- expect do
- post_graphql_mutation(mutation, current_user: current_user)
- work_item.reload
- end.to change(work_item, :description).from(nil).to('updated description')
-
- expect(response).to have_gitlab_http_status(:success)
- expect(mutation_response['workItem']).to include(
- 'title' => work_item.title
- )
+ it_behaves_like 'update work item description widget' do
+ let(:new_description) { 'updated description' }
end
it_behaves_like 'has spam protection' do
@@ -69,7 +52,7 @@ RSpec.describe 'Update work item widgets' do
expect do
post_graphql_mutation(mutation, current_user: current_user)
work_item.reload
- end.to not_change(work_item, :title)
+ end.to not_change(work_item, :description)
expect(mutation_response['errors']).to contain_exactly('`work_items` feature flag disabled for this project')
end
diff --git a/spec/requests/api/graphql/project/container_repositories_spec.rb b/spec/requests/api/graphql/project/container_repositories_spec.rb
index bbab6012f3f..01b117a89d8 100644
--- a/spec/requests/api/graphql/project/container_repositories_spec.rb
+++ b/spec/requests/api/graphql/project/container_repositories_spec.rb
@@ -81,7 +81,7 @@ RSpec.describe 'getting container repositories in a project' do
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
+ project.add_member(user, role) unless role == :anonymous
end
it 'return the proper response' do
diff --git a/spec/requests/api/graphql/project/issues_spec.rb b/spec/requests/api/graphql/project/issues_spec.rb
index 69e14eace66..596e023a027 100644
--- a/spec/requests/api/graphql/project/issues_spec.rb
+++ b/spec/requests/api/graphql/project/issues_spec.rb
@@ -223,6 +223,7 @@ RSpec.describe 'getting an issue list for a project' do
end
describe 'sorting and pagination' do
+ let_it_be(:sort_project) { create(:project, :public) }
let_it_be(:data_path) { [:project, :issues] }
def pagination_query(params)
@@ -237,8 +238,38 @@ RSpec.describe 'getting an issue list for a project' do
data.map { |issue| issue['iid'].to_i }
end
+ context 'when sorting by severity' do
+ let_it_be(:severty_issue1) { create(:issue, project: sort_project) }
+ let_it_be(:severty_issue2) { create(:issue, project: sort_project) }
+ let_it_be(:severty_issue3) { create(:issue, project: sort_project) }
+ let_it_be(:severty_issue4) { create(:issue, project: sort_project) }
+ let_it_be(:severty_issue5) { create(:issue, project: sort_project) }
+
+ before(:all) do
+ create(:issuable_severity, issue: severty_issue1, severity: :unknown)
+ create(:issuable_severity, issue: severty_issue2, severity: :low)
+ create(:issuable_severity, issue: severty_issue4, severity: :critical)
+ create(:issuable_severity, issue: severty_issue5, severity: :high)
+ end
+
+ context 'when ascending' do
+ it_behaves_like 'sorted paginated query' do
+ let(:sort_param) { :SEVERITY_ASC }
+ let(:first_param) { 2 }
+ let(:all_records) { [severty_issue3.iid, severty_issue1.iid, severty_issue2.iid, severty_issue5.iid, severty_issue4.iid] }
+ end
+ end
+
+ context 'when descending' do
+ it_behaves_like 'sorted paginated query' do
+ let(:sort_param) { :SEVERITY_DESC }
+ let(:first_param) { 2 }
+ let(:all_records) { [severty_issue4.iid, severty_issue5.iid, severty_issue2.iid, severty_issue1.iid, severty_issue3.iid] }
+ end
+ end
+ end
+
context 'when sorting by due date' do
- let_it_be(:sort_project) { create(:project, :public) }
let_it_be(:due_issue1) { create(:issue, project: sort_project, due_date: 3.days.from_now) }
let_it_be(:due_issue2) { create(:issue, project: sort_project, due_date: nil) }
let_it_be(:due_issue3) { create(:issue, project: sort_project, due_date: 2.days.ago) }
@@ -263,7 +294,6 @@ RSpec.describe 'getting an issue list for a project' do
end
context 'when sorting by relative position' do
- let_it_be(:sort_project) { create(:project, :public) }
let_it_be(:relative_issue1) { create(:issue, project: sort_project, relative_position: 2000) }
let_it_be(:relative_issue2) { create(:issue, project: sort_project, relative_position: nil) }
let_it_be(:relative_issue3) { create(:issue, project: sort_project, relative_position: 1000) }
@@ -285,7 +315,6 @@ RSpec.describe 'getting an issue list for a project' do
end
context 'when sorting by priority' do
- let_it_be(:sort_project) { create(:project, :public) }
let_it_be(:on_project) { { project: sort_project } }
let_it_be(:early_milestone) { create(:milestone, **on_project, due_date: 10.days.from_now) }
let_it_be(:late_milestone) { create(:milestone, **on_project, due_date: 30.days.from_now) }
@@ -321,7 +350,6 @@ RSpec.describe 'getting an issue list for a project' do
end
context 'when sorting by label priority' do
- let_it_be(:sort_project) { create(:project, :public) }
let_it_be(:label1) { create(:label, project: sort_project, priority: 1) }
let_it_be(:label2) { create(:label, project: sort_project, priority: 5) }
let_it_be(:label3) { create(:label, project: sort_project, priority: 10) }
@@ -348,7 +376,6 @@ RSpec.describe 'getting an issue list for a project' do
end
context 'when sorting by milestone due date' do
- let_it_be(:sort_project) { create(:project, :public) }
let_it_be(:early_milestone) { create(:milestone, project: sort_project, due_date: 10.days.from_now) }
let_it_be(:late_milestone) { create(:milestone, project: sort_project, due_date: 30.days.from_now) }
let_it_be(:milestone_issue1) { create(:issue, project: sort_project) }
diff --git a/spec/requests/api/graphql/project/jobs_spec.rb b/spec/requests/api/graphql/project/jobs_spec.rb
index 1a823ede9ac..7d0eb203d60 100644
--- a/spec/requests/api/graphql/project/jobs_spec.rb
+++ b/spec/requests/api/graphql/project/jobs_spec.rb
@@ -31,8 +31,8 @@ RSpec.describe 'Query.project.jobs' do
end
it 'does not generate N+1 queries', :request_store, :use_sql_query_cache do
- build_stage = create(:ci_stage_entity, position: 1, name: 'build', project: project, pipeline: pipeline)
- test_stage = create(:ci_stage_entity, position: 2, name: 'test', project: project, pipeline: pipeline)
+ build_stage = create(:ci_stage, position: 1, name: 'build', project: project, pipeline: pipeline)
+ test_stage = create(:ci_stage, position: 2, name: 'test', project: project, pipeline: pipeline)
create(:ci_build, pipeline: pipeline, stage_idx: build_stage.position, name: 'docker 1 2', stage: build_stage)
create(:ci_build, pipeline: pipeline, stage_idx: build_stage.position, name: 'docker 2 2', stage: build_stage)
create(:ci_build, pipeline: pipeline, stage_idx: test_stage.position, name: 'rspec 1 2', stage: test_stage)
diff --git a/spec/requests/api/graphql/project/packages_cleanup_policy_spec.rb b/spec/requests/api/graphql/project/packages_cleanup_policy_spec.rb
index a025c57d4b8..33e1dbcba27 100644
--- a/spec/requests/api/graphql/project/packages_cleanup_policy_spec.rb
+++ b/spec/requests/api/graphql/project/packages_cleanup_policy_spec.rb
@@ -61,7 +61,7 @@ RSpec.describe 'getting the packages cleanup policy linked to a project' do
with_them do
before do
project.update!(visibility: visibility.to_s)
- project.add_user(current_user, role) unless role == :anonymous
+ project.add_member(current_user, role) unless role == :anonymous
end
it 'return the proper response' do
diff --git a/spec/requests/api/graphql/project/pipeline_spec.rb b/spec/requests/api/graphql/project/pipeline_spec.rb
index ccf97918021..08c6a2d9927 100644
--- a/spec/requests/api/graphql/project/pipeline_spec.rb
+++ b/spec/requests/api/graphql/project/pipeline_spec.rb
@@ -105,6 +105,62 @@ RSpec.describe 'getting pipeline information nested in a project' do
end
end
+ context 'when a job has been retried' do
+ let_it_be(:retried) do
+ create(:ci_build, :retried,
+ name: build_job.name,
+ pipeline: pipeline,
+ stage_idx: 0,
+ stage: build_job.stage)
+ end
+
+ let(:fields) do
+ query_graphql_field(:jobs, { retried: retried_argument },
+ query_graphql_field(:nodes, {}, all_graphql_fields_for('CiJob', max_depth: 3)))
+ end
+
+ context 'when we filter out retried jobs' do
+ let(:retried_argument) { false }
+
+ it 'contains latest jobs' do
+ post_graphql(query, current_user: current_user)
+
+ expect(graphql_data_at(*path, :jobs, :nodes)).to include(
+ a_graphql_entity_for(build_job, :name, :duration, :retried)
+ )
+
+ expect(graphql_data_at(*path, :jobs, :nodes)).not_to include(
+ a_graphql_entity_for(retried)
+ )
+ end
+ end
+
+ context 'when we filter to only retried jobs' do
+ let(:retried_argument) { true }
+
+ it 'contains only retried jobs' do
+ post_graphql(query, current_user: current_user)
+
+ expect(graphql_data_at(*path, :jobs, :nodes)).to contain_exactly(
+ a_graphql_entity_for(retried)
+ )
+ end
+ end
+
+ context 'when we pass null explicitly' do
+ let(:retried_argument) { nil }
+
+ it 'contains all jobs' do
+ post_graphql(query, current_user: current_user)
+
+ expect(graphql_data_at(*path, :jobs, :nodes)).to include(
+ a_graphql_entity_for(build_job),
+ a_graphql_entity_for(retried)
+ )
+ end
+ end
+ end
+
context 'when requesting only builds with certain statuses' do
let(:variables) do
{
@@ -290,8 +346,8 @@ RSpec.describe 'getting pipeline information nested in a project' do
end
it 'does not generate N+1 queries', :request_store, :use_sql_query_cache do
- build_stage = create(:ci_stage_entity, position: 1, name: 'build', project: project, pipeline: pipeline)
- test_stage = create(:ci_stage_entity, position: 2, name: 'test', project: project, pipeline: pipeline)
+ build_stage = create(:ci_stage, position: 1, name: 'build', project: project, pipeline: pipeline)
+ test_stage = create(:ci_stage, position: 2, name: 'test', project: project, pipeline: pipeline)
create(:ci_build, pipeline: pipeline, stage_idx: build_stage.position, name: 'docker 1 2', stage: build_stage)
create(:ci_build, pipeline: pipeline, stage_idx: build_stage.position, name: 'docker 2 2', stage: build_stage)
create(:ci_build, pipeline: pipeline, stage_idx: test_stage.position, name: 'rspec 1 2', stage: test_stage)
diff --git a/spec/requests/api/graphql/project/project_members_spec.rb b/spec/requests/api/graphql/project/project_members_spec.rb
index c3281b44954..4225c3ad3e8 100644
--- a/spec/requests/api/graphql/project/project_members_spec.rb
+++ b/spec/requests/api/graphql/project/project_members_spec.rb
@@ -8,8 +8,8 @@ RSpec.describe 'getting project members information' do
let_it_be(:parent_group) { create(:group, :public) }
let_it_be(:parent_project) { create(:project, :public, group: parent_group) }
let_it_be(:user) { create(:user) }
- let_it_be(:user_1) { create(:user, username: 'user') }
- let_it_be(:user_2) { create(:user, username: 'test') }
+ let_it_be(:user_1) { create(:user, username: 'user', name: 'Same Name') }
+ let_it_be(:user_2) { create(:user, username: 'test', name: 'Same Name') }
before_all do
[user_1, user_2].each { |user| parent_group.add_guest(user) }
@@ -29,11 +29,44 @@ RSpec.describe 'getting project members information' do
expect_array_response(user_1, user_2)
end
- it 'returns members that match the search query' do
- fetch_members(project: parent_project, args: { search: 'test' })
+ describe 'search argument' do
+ it 'returns members that match the search query' do
+ fetch_members(project: parent_project, args: { search: 'test' })
- expect(graphql_errors).to be_nil
- expect_array_response(user_2)
+ expect(graphql_errors).to be_nil
+ expect_array_response(user_2)
+ end
+
+ context 'when paginating' do
+ it 'returns correct results' do
+ fetch_members(project: parent_project, args: { search: 'Same Name', first: 1 })
+
+ expect_array_response(user_1)
+
+ next_cursor = graphql_data_at(:project, :projectMembers, :pageInfo, :endCursor)
+ fetch_members(project: parent_project, args: { search: 'Same Name', first: 1, after: next_cursor })
+
+ expect_array_response(user_2)
+ end
+
+ context 'when the use_keyset_aware_user_search_query FF is off' do
+ before do
+ stub_feature_flags(use_keyset_aware_user_search_query: false)
+ end
+
+ it 'raises error on the 2nd page due to missing cursor data' do
+ fetch_members(project: parent_project, args: { search: 'Same Name', first: 1 })
+
+ # user_2 because the "old" order was undeterministic (insert order), no tie-breaker column
+ expect_array_response(user_2)
+
+ next_cursor = graphql_data_at(:project, :projectMembers, :pageInfo, :endCursor)
+ fetch_members(project: parent_project, args: { search: 'Same Name', first: 1, after: next_cursor })
+
+ expect(graphql_errors.first['message']).to include('PG::UndefinedColumn')
+ end
+ end
+ end
end
end
@@ -231,6 +264,9 @@ RSpec.describe 'getting project members information' do
}
}
}
+ pageInfo {
+ endCursor
+ }
NODE
graphql_query_for('project',
diff --git a/spec/requests/api/graphql/todo_query_spec.rb b/spec/requests/api/graphql/todo_query_spec.rb
new file mode 100644
index 00000000000..3f743f4402a
--- /dev/null
+++ b/spec/requests/api/graphql/todo_query_spec.rb
@@ -0,0 +1,50 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe 'Todo Query' do
+ include GraphqlHelpers
+
+ let_it_be(:current_user) { nil }
+ let_it_be(:project) { create(:project) }
+ let_it_be(:issue) { create(:issue, project: project) }
+
+ let_it_be(:todo_owner) { create(:user) }
+
+ let_it_be(:todo) { create(:todo, user: todo_owner, target: project) }
+
+ before do
+ project.add_developer(todo_owner)
+ end
+
+ let(:fields) do
+ <<~GRAPHQL
+ id
+ GRAPHQL
+ end
+
+ let(:query) do
+ graphql_query_for(:todo, { id: todo.to_global_id.to_s }, fields)
+ end
+
+ subject do
+ result = GitlabSchema.execute(query, context: { current_user: current_user }).to_h
+ graphql_dig_at(result, :data, :todo)
+ end
+
+ context 'when requesting user is todo owner' do
+ let(:current_user) { todo_owner }
+
+ it { is_expected.to include('id' => todo.to_global_id.to_s) }
+ end
+
+ context 'when requesting user is not todo owner' do
+ let(:current_user) { create(:user) }
+
+ it { is_expected.to be_nil }
+ end
+
+ context 'when unauthenticated' do
+ it { is_expected.to be_nil }
+ end
+end
diff --git a/spec/requests/api/graphql/work_item_spec.rb b/spec/requests/api/graphql/work_item_spec.rb
index 09bda8ee0d5..f17d2ebbb7e 100644
--- a/spec/requests/api/graphql/work_item_spec.rb
+++ b/spec/requests/api/graphql/work_item_spec.rb
@@ -8,7 +8,7 @@ RSpec.describe 'Query.work_item(id)' do
let_it_be(:developer) { create(:user) }
let_it_be(:guest) { create(:user) }
let_it_be(:project) { create(:project, :private) }
- let_it_be(:work_item) { create(:work_item, project: project, description: '- List item') }
+ let_it_be(:work_item) { create(:work_item, project: project, description: '- List item', weight: 1) }
let_it_be(:child_item1) { create(:work_item, :task, project: project) }
let_it_be(:child_item2) { create(:work_item, :task, confidential: true, project: project) }
let_it_be(:child_link1) { create(:parent_link, work_item_parent: work_item, work_item: child_item1) }
@@ -64,16 +64,13 @@ RSpec.describe 'Query.work_item(id)' do
it 'returns widget information' do
expect(work_item_data).to include(
'id' => work_item.to_gid.to_s,
- 'widgets' => match_array([
+ 'widgets' => include(
hash_including(
'type' => 'DESCRIPTION',
'description' => work_item.description,
'descriptionHtml' => ::MarkupHelper.markdown_field(work_item, :description, {})
- ),
- hash_including(
- 'type' => 'HIERARCHY'
)
- ])
+ )
)
end
end
@@ -101,10 +98,7 @@ RSpec.describe 'Query.work_item(id)' do
it 'returns widget information' do
expect(work_item_data).to include(
'id' => work_item.to_gid.to_s,
- 'widgets' => match_array([
- hash_including(
- 'type' => 'DESCRIPTION'
- ),
+ 'widgets' => include(
hash_including(
'type' => 'HIERARCHY',
'parent' => nil,
@@ -113,7 +107,7 @@ RSpec.describe 'Query.work_item(id)' do
hash_including('id' => child_link2.work_item.to_gid.to_s)
]) }
)
- ])
+ )
)
end
@@ -137,10 +131,7 @@ RSpec.describe 'Query.work_item(id)' do
it 'filters out not accessible children or parent' do
expect(work_item_data).to include(
'id' => work_item.to_gid.to_s,
- 'widgets' => match_array([
- hash_including(
- 'type' => 'DESCRIPTION'
- ),
+ 'widgets' => include(
hash_including(
'type' => 'HIERARCHY',
'parent' => nil,
@@ -148,7 +139,7 @@ RSpec.describe 'Query.work_item(id)' do
hash_including('id' => child_link1.work_item.to_gid.to_s)
]) }
)
- ])
+ )
)
end
end
@@ -160,20 +151,85 @@ RSpec.describe 'Query.work_item(id)' do
it 'returns parent information' do
expect(work_item_data).to include(
'id' => work_item.to_gid.to_s,
- 'widgets' => match_array([
- hash_including(
- 'type' => 'DESCRIPTION'
- ),
+ 'widgets' => include(
hash_including(
'type' => 'HIERARCHY',
'parent' => hash_including('id' => parent_link.work_item_parent.to_gid.to_s),
'children' => { 'nodes' => match_array([]) }
)
- ])
+ )
)
end
end
end
+
+ describe 'weight widget' do
+ let(:work_item_fields) do
+ <<~GRAPHQL
+ id
+ widgets {
+ type
+ ... on WorkItemWidgetWeight {
+ weight
+ }
+ }
+ GRAPHQL
+ end
+
+ it 'returns widget information' do
+ expect(work_item_data).to include(
+ 'id' => work_item.to_gid.to_s,
+ 'widgets' => include(
+ hash_including(
+ 'type' => 'WEIGHT',
+ 'weight' => work_item.weight
+ )
+ )
+ )
+ end
+ end
+
+ describe 'assignees widget' do
+ let(:assignees) { create_list(:user, 2) }
+ let(:work_item) { create(:work_item, project: project, assignees: assignees) }
+
+ let(:work_item_fields) do
+ <<~GRAPHQL
+ id
+ widgets {
+ type
+ ... on WorkItemWidgetAssignees {
+ allowsMultipleAssignees
+ canInviteMembers
+ assignees {
+ nodes {
+ id
+ username
+ }
+ }
+ }
+ }
+ GRAPHQL
+ end
+
+ it 'returns widget information' do
+ expect(work_item_data).to include(
+ 'id' => work_item.to_gid.to_s,
+ 'widgets' => include(
+ hash_including(
+ 'type' => 'ASSIGNEES',
+ 'allowsMultipleAssignees' => boolean,
+ 'canInviteMembers' => boolean,
+ 'assignees' => {
+ 'nodes' => match_array(
+ assignees.map { |a| { 'id' => a.to_gid.to_s, 'username' => a.username } }
+ )
+ }
+ )
+ )
+ )
+ end
+ end
end
context 'when an Issue Global ID is provided' do